package heap

import (
	"fmt"
	"hi-jvm/classfile"
	"hi-jvm/rtda"
)
/*
方法区：
1: 运行时数据区的一块逻辑区域，有多个线程共享。
2： 主要存放在从class文件获取的类信息，此外，类变量也存放在方法区中。
 */
type Method struct {
	ClassMember
	MaxStack  uint	// 操作栈的最大深度
	MaxLocals uint	// 局部变量表的大小
	Code      []byte	// 字节码
	ArgSlotCount uint	// 参数个数
}

func newMethods(class *Class, cfMethods []*classfile.MemberInfo) []*Method {
	methods := make([]*Method, len(cfMethods))
	for i, cfMethod := range cfMethods {
		methods[i] = &Method{}
		methods[i].Class = class
		methods[i].copyMemberInfo(cfMethod)
		methods[i].copyAttributes(cfMethod)
		methods[i].calcArgSlotCount()		// 计算参数个数
	}
	return methods
}

func (self *Method) copyAttributes(cfMethod *classfile.MemberInfo) {
	codeAttr := cfMethod.CodeAttribute();
	if codeAttr == nil {
		return
	}
	self.MaxStack = uint(codeAttr.MaxStack)
	self.MaxLocals = uint(codeAttr.MaxLocals)
	self.Code = codeAttr.Code
}

func (self *Method) calcArgSlotCount() {
	parsedDescriptor := parseMethodDescriptor(self.Descriptor)
	for _, paramType := range parsedDescriptor.ParameterTypes {
		self.ArgSlotCount++
		if paramType == "J" || paramType == "D" {
			self.ArgSlotCount++
		}
	}
	if !self.IsStatic() {	// 如果是成员变量，则第一个参数为实例对象自身
		self.ArgSlotCount++ // `this` reference
	}
}

func (self *Method)Check(name, descriptor string) bool {
	return  self.Name == name && self.Descriptor == descriptor
}


/*
定位到需要调用的方法后，
jvm要给这个方法创建新的帧，并推入栈顶
然后传递参数
需要4条方法调用指令
1: 创建新的帧并推入栈顶
2： 传递参数
*/
func  (self *Method)Invoke(invokerFrame *rtda.Frame) {
	thread := invokerFrame.Thread
	newFrame := thread.NewFrame(self)
	thread.PushFrame(newFrame)

	self.PushArgsToLocalArgs(invokerFrame, newFrame)

	// hack!
	if self.IsNative() {
		if self.Name == "registerNatives" {
			thread.PopFrame()
		} else {
			panic(fmt.Sprintf("native method: %v.%v%v\n",
				self.Class.Name, self.Name, self.Descriptor))
		}
	}
}

/*
传递参数
1： 确定参数在局部变量表占用多少位置： 注意这个不等于参数个数，因为
1） 有long和double
2） 成员函数，会在前面加一个this引用参数
2： 把n个位置的参数从调用者栈中弹出，放进被调用方法的局部变量表中
注意： 不需要对long和double进行特别处理
 */
func  (self *Method)PushArgsToLocalArgs(invokerFrame *rtda.Frame, newFrame *rtda.Frame) {
	argSlotCount := int(self.ArgSlotCount)
	if argSlotCount <= 0 {
		return
	}
	for i := argSlotCount - 1; i >= 0; i-- {
		slot := invokerFrame.OperandStack.PopSlot()
		newFrame.LocalVars.SetSlot(uint(i), slot)
	}
}

func (self *Method) IsNative() bool {
	return 0 != self.AccessFlags&ACC_NATIVE
}
func (self *Method) IsAbstract() bool {
	return 0 != self.AccessFlags&ACC_ABSTRACT
}

